/*
 * Copyright 2007 Sun Microsystems, Inc.  All Rights Reserved.
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
 *
 * This code is free software; you can redistribute it and/or modify it
 * under the terms of the GNU General Public License version 2 only, as
 * published by the Free Software Foundation.  Sun designates this
 * particular file as subject to the "Classpath" exception as provided
 * by Sun in the LICENSE file that accompanied this code.
 *
 * This code is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
 * version 2 for more details (a copy is included in the LICENSE file that
 * accompanied this code).
 *
 * You should have received a copy of the GNU General Public License version
 * 2 along with this work; if not, write to the Free Software Foundation,
 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
 *
 * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
 * CA 95054 USA or visit www.sun.com if you need additional information or
 * have any questions.
 */
package im.composer.media.sound.synth;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import javax.sound.midi.Patch;

/**
 * Soundfont instrument.
 *
 * @author Karl Helgason
 */
public class SF2Instrument extends ModelInstrument {

	protected String name = "";
	protected int preset = 0;
	protected int bank = 0;
	protected long library = 0;
	protected long genre = 0;
	protected long morphology = 0;
	protected SF2GlobalRegion globalregion = null;
	protected List<SF2InstrumentRegion> regions = new ArrayList<SF2InstrumentRegion>();

	public SF2Instrument() {
		super(null, null, null, null);
	}

	public SF2Instrument(SF2Soundbank soundbank) {
		super(soundbank, null, null, null);
	}

	public String getName() {
		return name;
	}

	public void setName(String name) {
		this.name = name;
	}

	public Patch getPatch() {
		if (bank == 128)
			return new ModelPatch(0, preset, true);
		else
			return new ModelPatch(bank << 7, preset, false);
	}

	public void setPatch(Patch patch) {
		if (patch instanceof ModelPatch && ((ModelPatch) patch).isPercussion()) {
			bank = 128;
			preset = patch.getProgram();
		} else {
			bank = patch.getBank() >> 7;
			preset = patch.getProgram();
		}
	}

	public Object getData() {
		return null;
	}

	public long getGenre() {
		return genre;
	}

	public void setGenre(long genre) {
		this.genre = genre;
	}

	public long getLibrary() {
		return library;
	}

	public void setLibrary(long library) {
		this.library = library;
	}

	public long getMorphology() {
		return morphology;
	}

	public void setMorphology(long morphology) {
		this.morphology = morphology;
	}

	public List<SF2InstrumentRegion> getRegions() {
		return regions;
	}

	public SF2GlobalRegion getGlobalRegion() {
		return globalregion;
	}

	public void setGlobalZone(SF2GlobalRegion zone) {
		globalregion = zone;
	}

	public String toString() {
		if (bank == 128)
			return "Drumkit: " + name + " preset #" + preset;
		else
			return "Instrument: " + name + " bank #" + bank + " preset #" + preset;
	}

	public ModelPerformer[] getPerformers() {
		int performercount = 0;
		for (SF2InstrumentRegion presetzone : regions)
			performercount += presetzone.getLayer().getRegions().size();
		ModelPerformer[] performers = new ModelPerformer[performercount];
		int pi = 0;

		SF2GlobalRegion presetglobal = globalregion;
		for (SF2InstrumentRegion presetzone : regions) {
			Map<Integer, Short> pgenerators = new HashMap<Integer, Short>();
			pgenerators.putAll(presetzone.getGenerators());
			if (presetglobal != null)
				pgenerators.putAll(presetglobal.getGenerators());

			SF2Layer layer = presetzone.getLayer();
			SF2GlobalRegion layerglobal = layer.getGlobalRegion();
			for (SF2LayerRegion layerzone : layer.getRegions()) {
				ModelPerformer performer = new ModelPerformer();
				if (layerzone.getSample() != null)
					performer.setName(layerzone.getSample().getName());
				else
					performer.setName(layer.getName());

				performers[pi++] = performer;

				int keyfrom = 0;
				int keyto = 127;
				int velfrom = 0;
				int velto = 127;

				if (layerzone.contains(SF2Region.GENERATOR_EXCLUSIVECLASS)) {
					performer.setExclusiveClass(layerzone.getInteger(SF2Region.GENERATOR_EXCLUSIVECLASS));
				}
				if (layerzone.contains(SF2Region.GENERATOR_KEYRANGE)) {
					byte[] bytes = layerzone.getBytes(SF2Region.GENERATOR_KEYRANGE);
					if (bytes[0] >= 0)
						if (bytes[0] > keyfrom)
							keyfrom = bytes[0];
					if (bytes[1] >= 0)
						if (bytes[1] < keyto)
							keyto = bytes[1];
				}
				if (layerzone.contains(SF2Region.GENERATOR_VELRANGE)) {
					byte[] bytes = layerzone.getBytes(SF2Region.GENERATOR_VELRANGE);
					if (bytes[0] >= 0)
						if (bytes[0] > velfrom)
							velfrom = bytes[0];
					if (bytes[1] >= 0)
						if (bytes[1] < velto)
							velto = bytes[1];
				}
				if (presetzone.contains(SF2Region.GENERATOR_KEYRANGE)) {
					byte[] bytes = presetzone.getBytes(SF2Region.GENERATOR_KEYRANGE);
					if (bytes[0] > keyfrom)
						keyfrom = bytes[0];
					if (bytes[1] < keyto)
						keyto = bytes[1];
				}
				if (presetzone.contains(SF2Region.GENERATOR_VELRANGE)) {
					byte[] bytes = presetzone.getBytes(SF2Region.GENERATOR_VELRANGE);
					if (bytes[0] > velfrom)
						velfrom = bytes[0];
					if (bytes[1] < velto)
						velto = bytes[1];
				}
				performer.setKeyFrom(keyfrom);
				performer.setKeyTo(keyto);
				performer.setVelFrom(velfrom);
				performer.setVelTo(velto);

				int startAddrsOffset = layerzone.getShort(SF2Region.GENERATOR_STARTADDRSOFFSET);
				int endAddrsOffset = layerzone.getShort(SF2Region.GENERATOR_ENDADDRSOFFSET);
				int startloopAddrsOffset = layerzone.getShort(SF2Region.GENERATOR_STARTLOOPADDRSOFFSET);
				int endloopAddrsOffset = layerzone.getShort(SF2Region.GENERATOR_ENDLOOPADDRSOFFSET);

				startAddrsOffset += layerzone.getShort(SF2Region.GENERATOR_STARTADDRSCOARSEOFFSET) * 32768;
				endAddrsOffset += layerzone.getShort(SF2Region.GENERATOR_ENDADDRSCOARSEOFFSET) * 32768;
				startloopAddrsOffset += layerzone.getShort(SF2Region.GENERATOR_STARTLOOPADDRSCOARSEOFFSET) * 32768;
				endloopAddrsOffset += layerzone.getShort(SF2Region.GENERATOR_ENDLOOPADDRSCOARSEOFFSET) * 32768;
				startloopAddrsOffset -= startAddrsOffset;
				endloopAddrsOffset -= startAddrsOffset;

				SF2Sample sample = layerzone.getSample();
				int rootkey = sample.originalPitch;
				if (layerzone.getShort(SF2Region.GENERATOR_OVERRIDINGROOTKEY) != -1) {
					rootkey = layerzone.getShort(SF2Region.GENERATOR_OVERRIDINGROOTKEY);
				}
				float pitchcorrection = (-rootkey * 100) + sample.pitchCorrection;
				ModelByteBuffer buff = sample.getDataBuffer();
				ModelByteBuffer buff24 = sample.getData24Buffer();

				if (startAddrsOffset != 0 || endAddrsOffset != 0) {
					buff = buff.subbuffer(startAddrsOffset * 2, buff.capacity() + endAddrsOffset * 2);
					if (buff24 != null) {
						buff24 = buff24.subbuffer(startAddrsOffset, buff24.capacity() + endAddrsOffset);
					}

					/*
					 * if (startAddrsOffset < 0) startAddrsOffset = 0; if (endAddrsOffset > (buff.capacity()/2-startAddrsOffset)) startAddrsOffset = (int)buff.capacity()/2-startAddrsOffset; byte[]
					 * data = buff.array(); int off = (int)buff.arrayOffset() + startAddrsOffset*2; int len = (int)buff.capacity() + endAddrsOffset*2; if (off+len > data.length) len = data.length -
					 * off; buff = new ModelByteBuffer(data, off, len); if(buff24 != null) { data = buff.array(); off = (int)buff.arrayOffset() + startAddrsOffset; len = (int)buff.capacity() +
					 * endAddrsOffset; buff24 = new ModelByteBuffer(data, off, len); }
					 */
				}

				ModelByteBufferWavetable osc = new ModelByteBufferWavetable(buff, sample.getFormat(), pitchcorrection);
				if (buff24 != null)
					osc.set8BitExtensionBuffer(buff24);

				Map<Integer, Short> generators = new HashMap<Integer, Short>();
				if (layerglobal != null)
					generators.putAll(layerglobal.getGenerators());
				generators.putAll(layerzone.getGenerators());
				for (Map.Entry<Integer, Short> gen : pgenerators.entrySet()) {
					short val;
					if (!generators.containsKey(gen.getKey()))
						val = layerzone.getShort(gen.getKey());
					else
						val = generators.get(gen.getKey());
					val += gen.getValue();
					generators.put(gen.getKey(), val);
				}

				// SampleMode:
				// 0 indicates a sound reproduced with no loop
				// 1 indicates a sound which loops continuously
				// 2 is unused but should be interpreted as indicating no loop
				// 3 indicates a sound which loops for the duration of key
				// depression then proceeds to play the remainder of the sample.
				int sampleMode = getGeneratorValue(generators, SF2Region.GENERATOR_SAMPLEMODES);
				if ((sampleMode == 1) || (sampleMode == 3)) {
					if (sample.startLoop >= 0 && sample.endLoop > 0) {
						osc.setLoopStart((int) (sample.startLoop + startloopAddrsOffset));
						osc.setLoopLength((int) (sample.endLoop - sample.startLoop + endloopAddrsOffset - startloopAddrsOffset));
						if (sampleMode == 1)
							osc.setLoopType(ModelWavetable.LOOP_TYPE_FORWARD);
						if (sampleMode == 3)
							osc.setLoopType(ModelWavetable.LOOP_TYPE_RELEASE);
					}
				}
				performer.getOscillators().add(osc);

				short volDelay = getGeneratorValue(generators, SF2Region.GENERATOR_DELAYVOLENV);
				short volAttack = getGeneratorValue(generators, SF2Region.GENERATOR_ATTACKVOLENV);
				short volHold = getGeneratorValue(generators, SF2Region.GENERATOR_HOLDVOLENV);
				short volDecay = getGeneratorValue(generators, SF2Region.GENERATOR_DECAYVOLENV);
				short volSustain = getGeneratorValue(generators, SF2Region.GENERATOR_SUSTAINVOLENV);
				short volRelease = getGeneratorValue(generators, SF2Region.GENERATOR_RELEASEVOLENV);

				if (volHold != -12000) {
					short volKeyNumToHold = getGeneratorValue(generators, SF2Region.GENERATOR_KEYNUMTOVOLENVHOLD);
					volHold += 60 * volKeyNumToHold;
					float fvalue = -volKeyNumToHold * 128;
					ModelIdentifier src = ModelSource.SOURCE_NOTEON_KEYNUMBER;
					ModelIdentifier dest = ModelDestination.DESTINATION_EG1_HOLD;
					performer.getConnectionBlocks().add(new ModelConnectionBlock(new ModelSource(src), fvalue, new ModelDestination(dest)));
				}
				if (volDecay != -12000) {
					short volKeyNumToDecay = getGeneratorValue(generators, SF2Region.GENERATOR_KEYNUMTOVOLENVDECAY);
					volDecay += 60 * volKeyNumToDecay;
					float fvalue = -volKeyNumToDecay * 128;
					ModelIdentifier src = ModelSource.SOURCE_NOTEON_KEYNUMBER;
					ModelIdentifier dest = ModelDestination.DESTINATION_EG1_DECAY;
					performer.getConnectionBlocks().add(new ModelConnectionBlock(new ModelSource(src), fvalue, new ModelDestination(dest)));
				}

				addTimecentValue(performer, ModelDestination.DESTINATION_EG1_DELAY, volDelay);
				addTimecentValue(performer, ModelDestination.DESTINATION_EG1_ATTACK, volAttack);
				addTimecentValue(performer, ModelDestination.DESTINATION_EG1_HOLD, volHold);
				addTimecentValue(performer, ModelDestination.DESTINATION_EG1_DECAY, volDecay);
				// float fvolsustain = (960-volSustain)*(1000.0f/960.0f);

				volSustain = (short) (1000 - volSustain);
				if (volSustain < 0)
					volSustain = 0;
				if (volSustain > 1000)
					volSustain = 1000;

				addValue(performer, ModelDestination.DESTINATION_EG1_SUSTAIN, volSustain);
				addTimecentValue(performer, ModelDestination.DESTINATION_EG1_RELEASE, volRelease);

				if (getGeneratorValue(generators, SF2Region.GENERATOR_MODENVTOFILTERFC) != 0 || getGeneratorValue(generators, SF2Region.GENERATOR_MODENVTOPITCH) != 0) {
					short modDelay = getGeneratorValue(generators, SF2Region.GENERATOR_DELAYMODENV);
					short modAttack = getGeneratorValue(generators, SF2Region.GENERATOR_ATTACKMODENV);
					short modHold = getGeneratorValue(generators, SF2Region.GENERATOR_HOLDMODENV);
					short modDecay = getGeneratorValue(generators, SF2Region.GENERATOR_DECAYMODENV);
					short modSustain = getGeneratorValue(generators, SF2Region.GENERATOR_SUSTAINMODENV);
					short modRelease = getGeneratorValue(generators, SF2Region.GENERATOR_RELEASEMODENV);

					if (modHold != -12000) {
						short modKeyNumToHold = getGeneratorValue(generators, SF2Region.GENERATOR_KEYNUMTOMODENVHOLD);
						modHold += 60 * modKeyNumToHold;
						float fvalue = -modKeyNumToHold * 128;
						ModelIdentifier src = ModelSource.SOURCE_NOTEON_KEYNUMBER;
						ModelIdentifier dest = ModelDestination.DESTINATION_EG2_HOLD;
						performer.getConnectionBlocks().add(new ModelConnectionBlock(new ModelSource(src), fvalue, new ModelDestination(dest)));
					}
					if (modDecay != -12000) {
						short modKeyNumToDecay = getGeneratorValue(generators, SF2Region.GENERATOR_KEYNUMTOMODENVDECAY);
						modDecay += 60 * modKeyNumToDecay;
						float fvalue = -modKeyNumToDecay * 128;
						ModelIdentifier src = ModelSource.SOURCE_NOTEON_KEYNUMBER;
						ModelIdentifier dest = ModelDestination.DESTINATION_EG2_DECAY;
						performer.getConnectionBlocks().add(new ModelConnectionBlock(new ModelSource(src), fvalue, new ModelDestination(dest)));
					}

					addTimecentValue(performer, ModelDestination.DESTINATION_EG2_DELAY, modDelay);
					addTimecentValue(performer, ModelDestination.DESTINATION_EG2_ATTACK, modAttack);
					addTimecentValue(performer, ModelDestination.DESTINATION_EG2_HOLD, modHold);
					addTimecentValue(performer, ModelDestination.DESTINATION_EG2_DECAY, modDecay);
					if (modSustain < 0)
						modSustain = 0;
					if (modSustain > 1000)
						modSustain = 1000;
					addValue(performer, ModelDestination.DESTINATION_EG2_SUSTAIN, 1000 - modSustain);
					addTimecentValue(performer, ModelDestination.DESTINATION_EG2_RELEASE, modRelease);

					if (getGeneratorValue(generators, SF2Region.GENERATOR_MODENVTOFILTERFC) != 0) {
						double fvalue = getGeneratorValue(generators, SF2Region.GENERATOR_MODENVTOFILTERFC);
						ModelIdentifier src = ModelSource.SOURCE_EG2;
						ModelIdentifier dest = ModelDestination.DESTINATION_FILTER_FREQ;
						performer.getConnectionBlocks().add(new ModelConnectionBlock(new ModelSource(src), fvalue, new ModelDestination(dest)));
					}

					if (getGeneratorValue(generators, SF2Region.GENERATOR_MODENVTOPITCH) != 0) {
						double fvalue = getGeneratorValue(generators, SF2Region.GENERATOR_MODENVTOPITCH);
						ModelIdentifier src = ModelSource.SOURCE_EG2;
						ModelIdentifier dest = ModelDestination.DESTINATION_PITCH;
						performer.getConnectionBlocks().add(new ModelConnectionBlock(new ModelSource(src), fvalue, new ModelDestination(dest)));
					}

				}

				if (getGeneratorValue(generators, SF2Region.GENERATOR_MODLFOTOFILTERFC) != 0 || getGeneratorValue(generators, SF2Region.GENERATOR_MODLFOTOPITCH) != 0
						|| getGeneratorValue(generators, SF2Region.GENERATOR_MODLFOTOVOLUME) != 0) {
					short lfo_freq = getGeneratorValue(generators, SF2Region.GENERATOR_FREQMODLFO);
					short lfo_delay = getGeneratorValue(generators, SF2Region.GENERATOR_DELAYMODLFO);
					addTimecentValue(performer, ModelDestination.DESTINATION_LFO1_DELAY, lfo_delay);
					addValue(performer, ModelDestination.DESTINATION_LFO1_FREQ, lfo_freq);
				}

				short vib_freq = getGeneratorValue(generators, SF2Region.GENERATOR_FREQVIBLFO);
				short vib_delay = getGeneratorValue(generators, SF2Region.GENERATOR_DELAYVIBLFO);
				addTimecentValue(performer, ModelDestination.DESTINATION_LFO2_DELAY, vib_delay);
				addValue(performer, ModelDestination.DESTINATION_LFO2_FREQ, vib_freq);

				if (getGeneratorValue(generators, SF2Region.GENERATOR_VIBLFOTOPITCH) != 0) {
					double fvalue = getGeneratorValue(generators, SF2Region.GENERATOR_VIBLFOTOPITCH);
					ModelIdentifier src = ModelSource.SOURCE_LFO2;
					ModelIdentifier dest = ModelDestination.DESTINATION_PITCH;
					performer.getConnectionBlocks()
							.add(new ModelConnectionBlock(new ModelSource(src, ModelStandardTransform.DIRECTION_MIN2MAX, ModelStandardTransform.POLARITY_BIPOLAR), fvalue, new ModelDestination(dest)));
				}

				if (getGeneratorValue(generators, SF2Region.GENERATOR_MODLFOTOFILTERFC) != 0) {
					double fvalue = getGeneratorValue(generators, SF2Region.GENERATOR_MODLFOTOFILTERFC);
					ModelIdentifier src = ModelSource.SOURCE_LFO1;
					ModelIdentifier dest = ModelDestination.DESTINATION_FILTER_FREQ;
					performer.getConnectionBlocks()
							.add(new ModelConnectionBlock(new ModelSource(src, ModelStandardTransform.DIRECTION_MIN2MAX, ModelStandardTransform.POLARITY_BIPOLAR), fvalue, new ModelDestination(dest)));
				}

				if (getGeneratorValue(generators, SF2Region.GENERATOR_MODLFOTOPITCH) != 0) {
					double fvalue = getGeneratorValue(generators, SF2Region.GENERATOR_MODLFOTOPITCH);
					ModelIdentifier src = ModelSource.SOURCE_LFO1;
					ModelIdentifier dest = ModelDestination.DESTINATION_PITCH;
					performer.getConnectionBlocks()
							.add(new ModelConnectionBlock(new ModelSource(src, ModelStandardTransform.DIRECTION_MIN2MAX, ModelStandardTransform.POLARITY_BIPOLAR), fvalue, new ModelDestination(dest)));
				}

				if (getGeneratorValue(generators, SF2Region.GENERATOR_MODLFOTOVOLUME) != 0) {
					double fvalue = getGeneratorValue(generators, SF2Region.GENERATOR_MODLFOTOVOLUME);
					ModelIdentifier src = ModelSource.SOURCE_LFO1;
					ModelIdentifier dest = ModelDestination.DESTINATION_GAIN;
					performer.getConnectionBlocks()
							.add(new ModelConnectionBlock(new ModelSource(src, ModelStandardTransform.DIRECTION_MIN2MAX, ModelStandardTransform.POLARITY_BIPOLAR), fvalue, new ModelDestination(dest)));
				}

				if (layerzone.getShort(SF2Region.GENERATOR_KEYNUM) != -1) {
					double val = layerzone.getShort(SF2Region.GENERATOR_KEYNUM) / 128.0;
					addValue(performer, ModelDestination.DESTINATION_KEYNUMBER, val);
				}

				if (layerzone.getShort(SF2Region.GENERATOR_VELOCITY) != -1) {
					double val = layerzone.getShort(SF2Region.GENERATOR_VELOCITY) / 128.0;
					addValue(performer, ModelDestination.DESTINATION_VELOCITY, val);
				}

				if (getGeneratorValue(generators, SF2Region.GENERATOR_INITIALFILTERFC) < 13500) {
					short filter_freq = getGeneratorValue(generators, SF2Region.GENERATOR_INITIALFILTERFC);
					short filter_q = getGeneratorValue(generators, SF2Region.GENERATOR_INITIALFILTERQ);
					addValue(performer, ModelDestination.DESTINATION_FILTER_FREQ, filter_freq);
					addValue(performer, ModelDestination.DESTINATION_FILTER_Q, filter_q);
				}

				int tune = 100 * getGeneratorValue(generators, SF2Region.GENERATOR_COARSETUNE);
				tune += getGeneratorValue(generators, SF2Region.GENERATOR_FINETUNE);
				if (tune != 0) {
					addValue(performer, ModelDestination.DESTINATION_PITCH, (short) tune);
				}
				if (getGeneratorValue(generators, SF2Region.GENERATOR_PAN) != 0) {
					short val = getGeneratorValue(generators, SF2Region.GENERATOR_PAN);
					addValue(performer, ModelDestination.DESTINATION_PAN, val);
				}
				if (getGeneratorValue(generators, SF2Region.GENERATOR_INITIALATTENUATION) != 0) {
					short val = getGeneratorValue(generators, SF2Region.GENERATOR_INITIALATTENUATION);
					addValue(performer, ModelDestination.DESTINATION_GAIN, -0.376287f * val);
				}
				if (getGeneratorValue(generators, SF2Region.GENERATOR_CHORUSEFFECTSSEND) != 0) {
					short val = getGeneratorValue(generators, SF2Region.GENERATOR_CHORUSEFFECTSSEND);
					addValue(performer, ModelDestination.DESTINATION_CHORUS, val);
				}
				if (getGeneratorValue(generators, SF2Region.GENERATOR_REVERBEFFECTSSEND) != 0) {
					short val = getGeneratorValue(generators, SF2Region.GENERATOR_REVERBEFFECTSSEND);
					addValue(performer, ModelDestination.DESTINATION_REVERB, val);
				}
				if (getGeneratorValue(generators, SF2Region.GENERATOR_SCALETUNING) != 100) {
					short fvalue = getGeneratorValue(generators, SF2Region.GENERATOR_SCALETUNING);
					if (fvalue == 0) {
						ModelIdentifier dest = ModelDestination.DESTINATION_PITCH;
						performer.getConnectionBlocks().add(new ModelConnectionBlock(null, rootkey * 100, new ModelDestination(dest)));
					} else {
						ModelIdentifier dest = ModelDestination.DESTINATION_PITCH;
						performer.getConnectionBlocks().add(new ModelConnectionBlock(null, rootkey * (100 - fvalue), new ModelDestination(dest)));
					}

					ModelIdentifier src = ModelSource.SOURCE_NOTEON_KEYNUMBER;
					ModelIdentifier dest = ModelDestination.DESTINATION_PITCH;
					performer.getConnectionBlocks().add(new ModelConnectionBlock(new ModelSource(src), 128 * fvalue, new ModelDestination(dest)));

				}

				performer.getConnectionBlocks().add(new ModelConnectionBlock(new ModelSource(ModelSource.SOURCE_NOTEON_VELOCITY, new ModelTransform() {
					public double transform(double value) {
						if (value < 0.5)
							return 1 - value * 2;
						else
							return 0;
					}
				}), -2400, new ModelDestination(ModelDestination.DESTINATION_FILTER_FREQ)));

				performer.getConnectionBlocks()
						.add(new ModelConnectionBlock(
								new ModelSource(ModelSource.SOURCE_LFO2, ModelStandardTransform.DIRECTION_MIN2MAX, ModelStandardTransform.POLARITY_BIPOLAR, ModelStandardTransform.TRANSFORM_LINEAR),
								new ModelSource(new ModelIdentifier("midi_cc", "1", 0), ModelStandardTransform.DIRECTION_MIN2MAX, ModelStandardTransform.POLARITY_UNIPOLAR,
										ModelStandardTransform.TRANSFORM_LINEAR),
								50, new ModelDestination(ModelDestination.DESTINATION_PITCH)));

				if (layer.getGlobalRegion() != null) {
					for (SF2Modulator modulator : layer.getGlobalRegion().getModulators()) {
						convertModulator(performer, modulator);
					}
				}
				for (SF2Modulator modulator : layerzone.getModulators())
					convertModulator(performer, modulator);

				if (presetglobal != null) {
					for (SF2Modulator modulator : presetglobal.getModulators())
						convertModulator(performer, modulator);
				}
				for (SF2Modulator modulator : presetzone.getModulators())
					convertModulator(performer, modulator);

			}
		}
		return performers;
	}

	private void convertModulator(ModelPerformer performer, SF2Modulator modulator) {
		ModelSource src1 = convertSource(modulator.getSourceOperator());
		ModelSource src2 = convertSource(modulator.getAmountSourceOperator());
		if (src1 == null && modulator.getSourceOperator() != 0)
			return;
		if (src2 == null && modulator.getAmountSourceOperator() != 0)
			return;
		double amount = modulator.getAmount();
		double[] amountcorrection = new double[1];
		ModelSource[] extrasrc = new ModelSource[1];
		amountcorrection[0] = 1;
		ModelDestination dst = convertDestination(modulator.getDestinationOperator(), amountcorrection, extrasrc);
		amount *= amountcorrection[0];
		if (dst == null)
			return;
		if (modulator.getTransportOperator() == SF2Modulator.TRANSFORM_ABSOLUTE) {
			((ModelStandardTransform) dst.getTransform()).setTransform(ModelStandardTransform.TRANSFORM_ABSOLUTE);
		}
		ModelConnectionBlock conn = new ModelConnectionBlock(src1, src2, amount, dst);
		if (extrasrc[0] != null)
			conn.addSource(extrasrc[0]);
		performer.getConnectionBlocks().add(conn);

	}

	private static ModelSource convertSource(int src) {
		if (src == 0)
			return null;
		ModelIdentifier id = null;
		int idsrc = src & 0x7F;
		if ((src & SF2Modulator.SOURCE_MIDI_CONTROL) != 0) {
			id = new ModelIdentifier("midi_cc", Integer.toString(idsrc));
		} else {
			if (idsrc == SF2Modulator.SOURCE_NOTE_ON_VELOCITY)
				id = ModelSource.SOURCE_NOTEON_VELOCITY;
			if (idsrc == SF2Modulator.SOURCE_NOTE_ON_KEYNUMBER)
				id = ModelSource.SOURCE_NOTEON_KEYNUMBER;
			if (idsrc == SF2Modulator.SOURCE_POLY_PRESSURE)
				id = ModelSource.SOURCE_MIDI_POLY_PRESSURE;
			if (idsrc == SF2Modulator.SOURCE_CHANNEL_PRESSURE)
				id = ModelSource.SOURCE_MIDI_CHANNEL_PRESSURE;
			if (idsrc == SF2Modulator.SOURCE_PITCH_WHEEL)
				id = ModelSource.SOURCE_MIDI_PITCH;
			if (idsrc == SF2Modulator.SOURCE_PITCH_SENSITIVITY)
				id = new ModelIdentifier("midi_rpn", "0");
		}
		if (id == null)
			return null;

		ModelSource msrc = new ModelSource(id);
		ModelStandardTransform transform = (ModelStandardTransform) msrc.getTransform();

		if ((SF2Modulator.SOURCE_DIRECTION_MAX_MIN & src) != 0)
			transform.setDirection(ModelStandardTransform.DIRECTION_MAX2MIN);
		else
			transform.setDirection(ModelStandardTransform.DIRECTION_MIN2MAX);

		if ((SF2Modulator.SOURCE_POLARITY_BIPOLAR & src) != 0)
			transform.setPolarity(ModelStandardTransform.POLARITY_BIPOLAR);
		else
			transform.setPolarity(ModelStandardTransform.POLARITY_UNIPOLAR);

		if ((SF2Modulator.SOURCE_TYPE_CONCAVE & src) != 0)
			transform.setTransform(ModelStandardTransform.TRANSFORM_CONCAVE);
		if ((SF2Modulator.SOURCE_TYPE_CONVEX & src) != 0)
			transform.setTransform(ModelStandardTransform.TRANSFORM_CONVEX);
		if ((SF2Modulator.SOURCE_TYPE_SWITCH & src) != 0)
			transform.setTransform(ModelStandardTransform.TRANSFORM_SWITCH);

		return msrc;
	}

	protected static ModelDestination convertDestination(int dst, double[] amountcorrection, ModelSource[] extrasrc) {
		ModelIdentifier id = null;
		switch (dst) {
		case SF2Region.GENERATOR_INITIALFILTERFC:
			id = ModelDestination.DESTINATION_FILTER_FREQ;
			break;
		case SF2Region.GENERATOR_INITIALFILTERQ:
			id = ModelDestination.DESTINATION_FILTER_Q;
			break;
		case SF2Region.GENERATOR_CHORUSEFFECTSSEND:
			id = ModelDestination.DESTINATION_CHORUS;
			break;
		case SF2Region.GENERATOR_REVERBEFFECTSSEND:
			id = ModelDestination.DESTINATION_REVERB;
			break;
		case SF2Region.GENERATOR_PAN:
			id = ModelDestination.DESTINATION_PAN;
			break;
		case SF2Region.GENERATOR_DELAYMODLFO:
			id = ModelDestination.DESTINATION_LFO1_DELAY;
			break;
		case SF2Region.GENERATOR_FREQMODLFO:
			id = ModelDestination.DESTINATION_LFO1_FREQ;
			break;
		case SF2Region.GENERATOR_DELAYVIBLFO:
			id = ModelDestination.DESTINATION_LFO2_DELAY;
			break;
		case SF2Region.GENERATOR_FREQVIBLFO:
			id = ModelDestination.DESTINATION_LFO2_FREQ;
			break;

		case SF2Region.GENERATOR_DELAYMODENV:
			id = ModelDestination.DESTINATION_EG2_DELAY;
			break;
		case SF2Region.GENERATOR_ATTACKMODENV:
			id = ModelDestination.DESTINATION_EG2_ATTACK;
			break;
		case SF2Region.GENERATOR_HOLDMODENV:
			id = ModelDestination.DESTINATION_EG2_HOLD;
			break;
		case SF2Region.GENERATOR_DECAYMODENV:
			id = ModelDestination.DESTINATION_EG2_DECAY;
			break;
		case SF2Region.GENERATOR_SUSTAINMODENV:
			id = ModelDestination.DESTINATION_EG2_SUSTAIN;
			amountcorrection[0] = -1;
			break;
		case SF2Region.GENERATOR_RELEASEMODENV:
			id = ModelDestination.DESTINATION_EG2_RELEASE;
			break;
		case SF2Region.GENERATOR_DELAYVOLENV:
			id = ModelDestination.DESTINATION_EG1_DELAY;
			break;
		case SF2Region.GENERATOR_ATTACKVOLENV:
			id = ModelDestination.DESTINATION_EG1_ATTACK;
			break;
		case SF2Region.GENERATOR_HOLDVOLENV:
			id = ModelDestination.DESTINATION_EG1_HOLD;
			break;
		case SF2Region.GENERATOR_DECAYVOLENV:
			id = ModelDestination.DESTINATION_EG1_DECAY;
			break;
		case SF2Region.GENERATOR_SUSTAINVOLENV:
			id = ModelDestination.DESTINATION_EG1_SUSTAIN;
			amountcorrection[0] = -1;
			break;
		case SF2Region.GENERATOR_RELEASEVOLENV:
			id = ModelDestination.DESTINATION_EG1_RELEASE;
			break;
		case SF2Region.GENERATOR_KEYNUM:
			id = ModelDestination.DESTINATION_KEYNUMBER;
			break;
		case SF2Region.GENERATOR_VELOCITY:
			id = ModelDestination.DESTINATION_VELOCITY;
			break;

		case SF2Region.GENERATOR_COARSETUNE:
			amountcorrection[0] = 100;
			id = ModelDestination.DESTINATION_PITCH;
			break;

		case SF2Region.GENERATOR_FINETUNE:
			id = ModelDestination.DESTINATION_PITCH;
			break;

		case SF2Region.GENERATOR_INITIALATTENUATION:
			id = ModelDestination.DESTINATION_GAIN;
			amountcorrection[0] = -0.376287f;
			break;

		case SF2Region.GENERATOR_VIBLFOTOPITCH:
			id = ModelDestination.DESTINATION_PITCH;
			extrasrc[0] = new ModelSource(ModelSource.SOURCE_LFO2, ModelStandardTransform.DIRECTION_MIN2MAX, ModelStandardTransform.POLARITY_BIPOLAR);
			break;

		case SF2Region.GENERATOR_MODLFOTOPITCH:
			id = ModelDestination.DESTINATION_PITCH;
			extrasrc[0] = new ModelSource(ModelSource.SOURCE_LFO1, ModelStandardTransform.DIRECTION_MIN2MAX, ModelStandardTransform.POLARITY_BIPOLAR);
			break;

		case SF2Region.GENERATOR_MODLFOTOFILTERFC:
			id = ModelDestination.DESTINATION_FILTER_FREQ;
			extrasrc[0] = new ModelSource(ModelSource.SOURCE_LFO1, ModelStandardTransform.DIRECTION_MIN2MAX, ModelStandardTransform.POLARITY_BIPOLAR);
			break;

		case SF2Region.GENERATOR_MODLFOTOVOLUME:
			id = ModelDestination.DESTINATION_GAIN;
			amountcorrection[0] = -0.376287f;
			extrasrc[0] = new ModelSource(ModelSource.SOURCE_LFO1, ModelStandardTransform.DIRECTION_MIN2MAX, ModelStandardTransform.POLARITY_BIPOLAR);
			break;

		case SF2Region.GENERATOR_MODENVTOPITCH:
			id = ModelDestination.DESTINATION_PITCH;
			extrasrc[0] = new ModelSource(ModelSource.SOURCE_EG2, ModelStandardTransform.DIRECTION_MIN2MAX, ModelStandardTransform.POLARITY_BIPOLAR);
			break;

		case SF2Region.GENERATOR_MODENVTOFILTERFC:
			id = ModelDestination.DESTINATION_FILTER_FREQ;
			extrasrc[0] = new ModelSource(ModelSource.SOURCE_EG2, ModelStandardTransform.DIRECTION_MIN2MAX, ModelStandardTransform.POLARITY_BIPOLAR);
			break;

		default:
			break;
		}
		if (id != null)
			return new ModelDestination(id);
		return null;
	}

	private void addTimecentValue(ModelPerformer performer, ModelIdentifier dest, short value) {
		double fvalue;
		if (value == -12000)
			fvalue = Double.NEGATIVE_INFINITY;
		else
			fvalue = value;
		performer.getConnectionBlocks().add(new ModelConnectionBlock(fvalue, new ModelDestination(dest)));
	}

	private void addValue(ModelPerformer performer, ModelIdentifier dest, short value) {
		double fvalue = value;
		performer.getConnectionBlocks().add(new ModelConnectionBlock(fvalue, new ModelDestination(dest)));
	}

	private void addValue(ModelPerformer performer, ModelIdentifier dest, double value) {
		double fvalue = value;
		performer.getConnectionBlocks().add(new ModelConnectionBlock(fvalue, new ModelDestination(dest)));
	}

	private short getGeneratorValue(Map<Integer, Short> generators, int gen) {
		if (generators.containsKey(gen))
			return generators.get(gen);
		return SF2Region.getDefaultValue(gen);
	}
}
